aya: Support loading programs with ksyms#1372
aya: Support loading programs with ksyms#1372altugbozkurt07 wants to merge 1 commit intoaya-rs:mainfrom
Conversation
✅ Deploy Preview for aya-rs-docs ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
vadorovsky
left a comment
There was a problem hiding this comment.
Thanks for working on this and kudos for making it work!
The major issue causing the CI failures is usage of std which needs to be guarded.
I still need to play with the code, but I left some nitpicks about dosctrings and comments.
Reviewable status: 0 of 11 files reviewed, 25 unresolved discussions
aya-obj/src/lib.rs line 79 at r1 (raw file):
pub mod btf; /// ksym implementation
Let's remove this. If you want to have a top-level documentation of the module, add it in the top of the module file with !//.
aya-obj/src/extern_types.rs line 1 at r1 (raw file):
use std::{
This is the reason why CI is failing. The aya-obj crate supports no_std, so everything requiring std needs to be guarded with #[cfg(feature = "std")]. See the other modules
I still need to take a deeper look, but given that this whole ksym detection/sanitization mechanism clearly requires allocations and string operations, we could probably just guard the whole module with #[cfg(feature = "std")].
Another option would be trying to make it no_std, but I don't think it's possible.
aya-obj/src/extern_types.rs line 58 at r1 (raw file):
} // Dispatch to appropriate resolver
I would remove all the comments in this function except that one.
aya-obj/src/extern_types.rs line 99 at r1 (raw file):
fn resolve_kallsyms(&mut self) -> std::result::Result<(), KsymResolveError> { use std::{
Move the imports up.
aya-obj/src/extern_types.rs line 122 at r1 (raw file):
let file = File::open("/proc/kallsyms").map_err(|e| KsymResolveError::KallsymsReadError { error: e.to_string(),
We shouldn't include other errors with string.thiserror can wrap other error types inside your own one.
aya-obj/src/extern_types.rs line 227 at r1 (raw file):
/// Internal: Resolve a single extern variable /// Returns Some(btf_id) on success, None for weak externs not found
Code snippet:
/// Resolves a single exterm variable. Returns BTF ID if found, otherwise
/// returns `None`.aya/src/bpf.rs line 97 at r1 (raw file):
} pub(crate) static KERNEL_BTF: LazyLock<Option<Btf>> = LazyLock::new(|| match Btf::from_sys_fs() {
btw, I still need to play with the code myself, but I don't like having it as a global variable. If it's really difficult to pass it across functions, we could make it a part of some struct and write methods.
aya/src/bpf.rs line 1195 at r1 (raw file):
/// Error resolving extern kernel symbols (functions and variables) #[error("kernel symbol resolution error: {0}")] KsymResolve(#[from] aya_obj::KsymResolveError),
Usually we are trying to keep the error variants in EbpfError very general, and treat each variant as a category of errors. Given that these two are about ksyms, I would try to pack them in one error type first (let's stay, KsymError), then include it here.
test/integration-test/bpf/ksysm.bpf.c line 18 at r1 (raw file):
extern void bpf_rcu_read_lock(void) __attribute__((section(".ksyms"))); extern void bpf_rcu_read_unlock(void) __attribute__((section(".ksyms")));
Would be nice to add a similar Rust eBPF program.
aya-obj/src/btf/btf.rs line 819 at r1 (raw file):
/// This modifies extern functions and variables to make them acceptable to the kernel: /// - Functions: Change linkage to GLOBAL, fix param names, replace with dummy var in datasec /// - Variables: Change linkage to GLOBAL_ALLOCATED, replace type with int
Code snippet:
/// Fixes up BTF for `.ksyms` datasec entries containing extern kernel symbol and
/// makes it acceptable by the kernel:
///
/// * Changes linkage of extern functions to `GLOBAL`, fixes parameter names, injects
/// a dummy variable representing them in datasec.
/// * Changes linkage of extern variables to `GLOBAL_ALLOCATED`, replaces their type
/// with `int`.aya-obj/src/btf/btf.rs line 825 at r1 (raw file):
dummy_var_id: Option<u32>, ) -> Result<(), BtfError> { // Get dummy var info and int type ID upfront
I would try to make this comment more descriptive (for the whole let ... else block`) and remove all the others.
aya-obj/src/relocation.rs line 184 at r1 (raw file):
pub fn relocate_externs(&mut self) -> Result<(), EbpfRelocationError> { for (name, extern_desc) in &self.externs.externs { eprintln!(
Use debug!, instead of eprintln!
aya-obj/src/relocation.rs line 246 at r1 (raw file):
fn relocate_externs<'a, I: Iterator<Item = &'a Relocation>>( fun: &mut Function, // ← Different first parameter (not &mut self)
Remove this comment, it brings no value. It's not even a method, so why would it have self?
aya-obj/src/obj.rs line 872 at r1 (raw file):
}; info!("Found/created int type_id: {}", int_type_id);
I would make it debug! and print it only if we create the int type.
aya-obj/src/obj.rs line 890 at r1 (raw file):
// After creating dummy_var info!("Created dummy_var type_id: {}", dummy_var_id);
debug!
aya-obj/src/obj.rs line 898 at r1 (raw file):
} /// Collect extern kernel symbols from BTF DATASEC entries
Function/method documentation should be written using the 3rd person, so starting from "Collects". And if you use full sentences, finish them with ..
Let's also be consistent with capitalizing (or not capitalizing) names. We either write "DATASEC" or "datasec" everywhere. I'm leaning towards the latter, since it's just a convenient shortened form of "data section", not really a proper name.
Code snippet:
/// Collects extern kernel symbols from BTF datasec entries.aya-obj/src/obj.rs line 900 at r1 (raw file):
/// Collect extern kernel symbols from BTF DATASEC entries fn collect_ksyms_from_btf(&mut self) -> Result<(), ParseError> { // Find .ksyms DATASEC
Remove all these comments, the code is self-explanatory enough.
aya-obj/src/obj.rs line 927 at r1 (raw file):
} /// Find .ksyms DATASEC in BTF, returns (id, datasec) if found
Code snippet:
/// Searches for the `.ksyms` datasec in BTF, returns it if found.aya-obj/src/obj.rs line 945 at r1 (raw file):
} /// Check if datasec contains any functions
Code snippet:
/// Checks if datasec dontains any functions.aya-obj/src/obj.rs line 954 at r1 (raw file):
} /// Collect extern descriptors from datasec entries
Code snippet:
/// Collects extern descriptors from datasec entries.aya-obj/src/obj.rs line 961 at r1 (raw file):
for entry in &datasec.entries { let Some(extern_desc) = self.process_datasec_entry(btf, entry)? else { continue; // Skip non-extern entries
Remove this comment.
aya-obj/src/obj.rs line 970 at r1 (raw file):
} /// Process a single datasec entry, returning ExternDesc if it's an extern
Aside from what I mentioned earlier, try to use ``` for type names. If the types are inside the same crate, you can even link them like:
Code snippet:
/// Processes a single datasec entry, returns [`ExternDesc`] if it's an extern.aya-obj/src/obj.rs line 978 at r1 (raw file):
let btf_type = btf.type_by_id(entry.btf_type)?; // Extract extern info based on type
Remove these overly explicit comments.
aya-obj/src/obj.rs line 1017 at r1 (raw file):
} /// Apply BTF fixups for .ksyms DATASEC
Code snippet:
/// Applies BTF fixups for `.ksyms` datasec.aya-obj/src/obj.rs line 1026 at r1 (raw file):
} /// Helper to find symbol by name
Don't start function/method documentation with "Helper", use the 3rd person and explain what it does.
Code snippet:
/// Return a symbol with the given `name` from the symbol table.01a0eec to
4cd24e4
Compare
vadorovsky
left a comment
There was a problem hiding this comment.
Some more comments about code organization, the kind of comments you were asking for. 😄
644a227 to
c891bf0
Compare
a914f37 to
28c5b29
Compare
altugbozkurt07
left a comment
There was a problem hiding this comment.
Reviewable status: 0 of 15 files reviewed, 36 unresolved discussions (waiting on @vadorovsky)
aya/src/bpf.rs line 97 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
btw, I still need to play with the code myself, but I don't like having it as a global variable. If it's really difficult to pass it across functions, we could make it a part of some struct and write methods.
removed, now we are using already parsed kernel btf.
aya/src/bpf.rs line 1195 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Usually we are trying to keep the error variants in
EbpfErrorvery general, and treat each variant as a category of errors. Given that these two are about ksyms, I would try to pack them in one error type first (let's stay,KsymError), then include it here.
Done.
aya-obj/src/extern_types.rs line 1 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
This is the reason why CI is failing. The
aya-objcrate supportsno_std, so everything requiringstdneeds to be guarded with#[cfg(feature = "std")]. See the other modulesI still need to take a deeper look, but given that this whole ksym detection/sanitization mechanism clearly requires allocations and string operations, we could probably just guard the whole module with
#[cfg(feature = "std")].Another option would be trying to make it no_std, but I don't think it's possible.
Done.
aya-obj/src/extern_types.rs line 58 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
I would remove all the comments in this function except that one.
Done.
aya-obj/src/extern_types.rs line 99 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Move the imports up.
would not it be better to be stayed in the function scope since this is only enabled with std feature
aya-obj/src/extern_types.rs line 122 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
We shouldn't include other errors with string.
thiserrorcan wrap other error types inside your own one.
Done.
aya-obj/src/lib.rs line 79 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Let's remove this. If you want to have a top-level documentation of the module, add it in the top of the module file with
!//.
Done.
aya-obj/src/obj.rs line 872 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
I would make it
debug!and print it only if we create the int type.
Done.
aya-obj/src/obj.rs line 890 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
debug!
Done.
aya-obj/src/obj.rs line 898 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Function/method documentation should be written using the 3rd person, so starting from "Collects". And if you use full sentences, finish them with
..Let's also be consistent with capitalizing (or not capitalizing) names. We either write "DATASEC" or "datasec" everywhere. I'm leaning towards the latter, since it's just a convenient shortened form of "data section", not really a proper name.
Done.
aya-obj/src/obj.rs line 900 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Remove all these comments, the code is self-explanatory enough.
Done.
aya-obj/src/obj.rs line 961 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Remove this comment.
Done.
aya-obj/src/obj.rs line 970 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Aside from what I mentioned earlier, try to use ``` for type names. If the types are inside the same crate, you can even link them like:
Done.
aya-obj/src/obj.rs line 978 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Remove these overly explicit comments.
Done.
aya-obj/src/obj.rs line 1026 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Don't start function/method documentation with "Helper", use the 3rd person and explain what it does.
Done.
aya-obj/src/relocation.rs line 184 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Use
debug!, instead ofeprintln!
Done.
aya-obj/src/relocation.rs line 246 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Remove this comment, it brings no value. It's not even a method, so why would it have
self?
Done.
aya-obj/src/btf/btf.rs line 825 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
I would try to make this comment more descriptive (for the whole
let ... elseblock`) and remove all the others.
Done.
test/integration-test/bpf/ksysm.bpf.c line 18 at r1 (raw file):
Previously, vadorovsky (Michal R) wrote…
Would be nice to add a similar Rust eBPF program.
I added a similar rust program, but extern definitions debug information is not emitted by rustc so that the proper btf is generated by llvm. We need a thread in discord regarding this.
aya-obj/src/extern_types.rs line 227 at r1 (raw file):
/// Internal: Resolve a single extern variable /// Returns Some(btf_id) on success, None for weak externs not found
Done.
aya-obj/src/btf/btf.rs line 819 at r1 (raw file):
/// This modifies extern functions and variables to make them acceptable to the kernel: /// - Functions: Change linkage to GLOBAL, fix param names, replace with dummy var in datasec /// - Variables: Change linkage to GLOBAL_ALLOCATED, replace type with int
Done.
aya-obj/src/obj.rs line 927 at r1 (raw file):
} /// Find .ksyms DATASEC in BTF, returns (id, datasec) if found
Done.
aya-obj/src/obj.rs line 945 at r1 (raw file):
} /// Check if datasec contains any functions
Done.
aya-obj/src/obj.rs line 954 at r1 (raw file):
} /// Collect extern descriptors from datasec entries
Done.
aya-obj/src/obj.rs line 1017 at r1 (raw file):
} /// Apply BTF fixups for .ksyms DATASEC
Done.
|
|
||
| bpf_rcu_read_unlock(); | ||
| return 0; | ||
| } No newline at end of file |
| #include <vmlinux.h> | ||
| #include <bpf/bpf_core_read.h> | ||
| #include <bpf/bpf_helpers.h> | ||
| #include <bpf/bpf_tracing.h> |
There was a problem hiding this comment.
clang-format is complaining about the order of these imports. But vmlinux.h has to be first, since all the BPF headers depend on it.
In all other .c files we work around this issue by disabling clang-format for includes:
| #include <vmlinux.h> | |
| #include <bpf/bpf_core_read.h> | |
| #include <bpf/bpf_helpers.h> | |
| #include <bpf/bpf_tracing.h> | |
| // clang-format off | |
| #include <vmlinux.h> | |
| #include <bpf/bpf_core_read.h> | |
| #include <bpf/bpf_helpers.h> | |
| #include <bpf/bpf_tracing.h> | |
| // clang-format on |
28c5b29 to
3be1eba
Compare
a041e9c to
1527344
Compare
58c66e1 to
900f7ea
Compare
e9d8861 to
171d8a6
Compare
|
@vadorovsky , hi there i extended the test cases to cover the core use cases. So this should provide better consistency and confidence in overall ksyms implementation in this PR, and thankfully all tests are passing finally :). I would appreciate a review on the latest changes and lets see if there is anything else needed before merging this. |
d359a6e to
32b4cf4
Compare
| // // Strong typed ksyms require the symbol to be in kallsyms. | ||
| // // With CONFIG_KALLSYMS_ALL=n, only function symbols are exported, | ||
| // // not variables like bpf_prog_active. The verifier uses | ||
| // // bpf_kallsyms_lookup_name() internally and will reject the program | ||
| // // if the symbol address cannot be resolved. | ||
| // if !kallsyms_available() { | ||
| // eprintln!("skipping test, kallsyms not available"); | ||
| // return; | ||
| // } | ||
| // match kallsyms_find("bpf_prog_active") { | ||
| // Some(addr) if addr != 0 => { | ||
| // eprintln!("bpf_prog_active found in kallsyms at {:#x}", addr); | ||
| // } | ||
| // _ => { | ||
| // eprintln!( | ||
| // "skipping test: bpf_prog_active not in kallsyms \ | ||
| // (CONFIG_KALLSYMS_ALL=n? only function symbols exported)" | ||
| // ); | ||
| // return; | ||
| // } | ||
| // } |
There was a problem hiding this comment.
This check looks in principle good to me. I was initially thinking about parsing the kernel config, but I like the solution of looking for the kallsym better. Let's just polish it up a bit. there is no need to print stuff in case of success.
Also, I think we should get rid of the addr != 0 check. Did you see that actually happening (address being Some(0))? If so, we need to understand why - and also try to make sure that the broken case always returns None. If not, then let's just remove the check.
| // // Strong typed ksyms require the symbol to be in kallsyms. | |
| // // With CONFIG_KALLSYMS_ALL=n, only function symbols are exported, | |
| // // not variables like bpf_prog_active. The verifier uses | |
| // // bpf_kallsyms_lookup_name() internally and will reject the program | |
| // // if the symbol address cannot be resolved. | |
| // if !kallsyms_available() { | |
| // eprintln!("skipping test, kallsyms not available"); | |
| // return; | |
| // } | |
| // match kallsyms_find("bpf_prog_active") { | |
| // Some(addr) if addr != 0 => { | |
| // eprintln!("bpf_prog_active found in kallsyms at {:#x}", addr); | |
| // } | |
| // _ => { | |
| // eprintln!( | |
| // "skipping test: bpf_prog_active not in kallsyms \ | |
| // (CONFIG_KALLSYMS_ALL=n? only function symbols exported)" | |
| // ); | |
| // return; | |
| // } | |
| // } | |
| // Strong typed ksyms require the symbol to be in kallsyms. | |
| // With CONFIG_KALLSYMS_ALL=n, only function symbols are exported, | |
| // not variables like bpf_prog_active. The verifier uses | |
| // bpf_kallsyms_lookup_name() internally and will reject the program | |
| // if the symbol address cannot be resolved. | |
| if !kallsyms_available() { | |
| eprintln!("skipping test, kallsyms not available"); | |
| return; | |
| } | |
| if kallsyms_find("bpf_prog_active").is_none() { | |
| eprintln!( | |
| "skipping test, bpf_prog_active kallsyms not available | |
| (usually due to CONFIG_KALLSYMS_ALL kernel configuration disabled)" | |
| ); | |
| return; | |
| } |
test-distro/src/init.rs
Outdated
|
|
||
| use anyhow::Context as _; | ||
|
|
||
| /// Dump kernel configuration and kallsyms info relevant to ksym tests. |
There was a problem hiding this comment.
This was definitely useful for your local debugging, but given that you've found the solution, can we remove this?
| fn kallsyms_available() -> bool { | ||
| File::open("/proc/kallsyms") | ||
| .ok() | ||
| .map(|f| { | ||
| BufReader::new(f) | ||
| .lines() | ||
| .take(100) | ||
| .filter_map(|l| l.ok()) | ||
| .filter_map(|l| l.split_whitespace().next().map(String::from)) | ||
| .filter_map(|s| u64::from_str_radix(&s, 16).ok()) | ||
| .any(|addr| addr != 0) | ||
| }) | ||
| .unwrap_or(false) | ||
| } | ||
|
|
||
| fn kallsyms_find(symbol_name: &str) -> Option<u64> { | ||
| let file = File::open("/proc/kallsyms").ok()?; | ||
| for line in BufReader::new(file).lines().filter_map(|l| l.ok()) { | ||
| let parts: Vec<&str> = line.split_whitespace().collect(); | ||
| if parts.len() >= 3 && parts[2] == symbol_name { | ||
| return u64::from_str_radix(parts[0], 16).ok(); | ||
| } | ||
| } | ||
| None | ||
| } |
There was a problem hiding this comment.
What's the point of take(100)? I don't think it's correct, there is no guarantee that the kallsyms we look for are in the first N of entries. Reading the whole file makes the test execution slightly longer, but it's still nothing noticeable.
Ignoring errors with .ok() is not great for debugging, in case something goes wrong. Imagine if the format of kallsyms changes some day, or if the current parsing mechanism does not always work with all configurations. With your current code, it will just throw None and one will have to spend time debugging. If we threw errors instead, it would be more clear which line failed. If we add some context to the errors, we can make the reason behind the failure crystal clear.
In tests, we are panicking instead of using anyhow, so we either:
- Use
.unwrap()orexpect(). But the downside of.unwrap()is that we can't add more context. The downside ofexpect()is that it's being evaluated even if the error is not hit, which is fine if you pass a simple string (e.g..expect("my message")), but is not fine once you are formatting arguments (e.g..expect(format!("failed to read from file {file}"))), because then theformat!call will be triggered unconditionally. - My preference - the following
unwrap_or_else(|e| panic!("message: {e:?}"))pattern (so the formatting happens only if we actually hit the error) to add context to the errors before panicking:
aya/test/integration-test/src/utils.rs
Lines 180 to 181 in be872b1
Ignoring errors that are OK to happen (due to the kernel not supporting a certain thing etc.) is fine, but from what I see, a failure in any of your .ok() calls you're issuing would mean a bug in your parsing logic. With a kernel with no kallsyms, we should be still able to parse stuff until we hit addr != 0.
Your attempt to use lazy iterators and filter_map chains is noble, but I think it's not great for emitting errors, so I wouldn't mind a regular loop.
Also, the parts of the kallsyms entry could be parsed by using slice pattern matching instead of soing the manual if parts.len() check. You can replace:
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 && parts[2] == symbol_name {
[...]
}with:
if let [addr, _type, name, ..] = parts.as_slice()
&& *name == symbol_name
{
[...]
}To sum it up, my proposed change:
| fn kallsyms_available() -> bool { | |
| File::open("/proc/kallsyms") | |
| .ok() | |
| .map(|f| { | |
| BufReader::new(f) | |
| .lines() | |
| .take(100) | |
| .filter_map(|l| l.ok()) | |
| .filter_map(|l| l.split_whitespace().next().map(String::from)) | |
| .filter_map(|s| u64::from_str_radix(&s, 16).ok()) | |
| .any(|addr| addr != 0) | |
| }) | |
| .unwrap_or(false) | |
| } | |
| fn kallsyms_find(symbol_name: &str) -> Option<u64> { | |
| let file = File::open("/proc/kallsyms").ok()?; | |
| for line in BufReader::new(file).lines().filter_map(|l| l.ok()) { | |
| let parts: Vec<&str> = line.split_whitespace().collect(); | |
| if parts.len() >= 3 && parts[2] == symbol_name { | |
| return u64::from_str_radix(parts[0], 16).ok(); | |
| } | |
| } | |
| None | |
| } | |
| const PROC_KALLSYMS: &str = "/proc/kallsyms"; | |
| fn kallsyms_available() -> bool { | |
| let file = File::open(PROC_KALLSYMS) | |
| .unwrap_or_else(|e| panic!("failed to open {PROC_KALLSYMS}: {e:?}")); | |
| for line in BufReader::new(file).lines() { | |
| let line = | |
| line.unwrap_or_else(|e| panic!("failed to read the line from {PROC_KALLSYMS}: {e:?}")); | |
| if let Some(addr) = line.split_whitespace().next() { | |
| let addr = u64::from_str_radix(addr, 16) | |
| .unwrap_or_else(|e| panic!("failed to parse the address: {addr}: {e:?}")); | |
| if addr != 0 { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| fn kallsyms_find(symbol_name: &str) -> Option<u64> { | |
| let file = File::open(PROC_KALLSYMS) | |
| .unwrap_or_else(|e| panic!("failed to open {PROC_KALLSYMS}: {e:?}")); | |
| for line in BufReader::new(file).lines() { | |
| let line = | |
| line.unwrap_or_else(|e| panic!("failed to read the line from {PROC_KALLSYMS}: {e:?}")); | |
| let parts: Vec<&str> = line.split_whitespace().collect(); | |
| if let [addr, _type, name, ..] = parts.as_slice() | |
| && *name == symbol_name | |
| { | |
| let addr = u64::from_str_radix(addr, 16) | |
| .unwrap_or_else(|e| panic!("failed to parse the address: {addr}: {e:?}")); | |
| return Some(addr); | |
| } | |
| } | |
| None | |
| } |
| let btf = match Btf::from_sys_fs() { | ||
| Ok(b) => b, | ||
| Err(_) => return false, | ||
| }; |
There was a problem hiding this comment.
Other tests have a hard unwrap on Btf::from_sys_fs. I think it's fine if you just unwrap or expect:
Besides, I think the Btf::from_sys_fs should be removed in this function and instead you should pass it as an argument, so we don't parse BTF multiple times. See the comment below.
| .try_into() | ||
| .unwrap(); | ||
|
|
||
| let btf = Btf::from_sys_fs().unwrap(); |
There was a problem hiding this comment.
You could move this up before if !btf_has_percpu_datasec() and make the tf_has_percpu_datasec function take &Btf, so we don't call Btf::from_sys_fs() multiple times.
| if let Err(e) = prog.load("sys_enter", &btf) { | ||
| panic!("load error: {e:?}"); | ||
| } |
There was a problem hiding this comment.
Since you are adding context to the error (nice), let's improve the message?
| if let Err(e) = prog.load("sys_enter", &btf) { | |
| panic!("load error: {e:?}"); | |
| } | |
| const SYS_ENTER: &str = "sys_enter"; | |
| if let Err(e) = prog.load(SYS_ENTER, &btf) { | |
| panic!("failed to load program {SYS_ENTER}: {e:?}"); | |
| } |
| .unwrap(); | ||
| assert!( | ||
| per_cpu_addr != 0, | ||
| "bpf_per_cpu_ptr address should be non-zero" |
There was a problem hiding this comment.
I like the descriptive messages in asserts. 👍
ef42763 to
17151ae
Compare
Hi,
this commit covers the first iteration of supporting programs with ksyms variables in aya. I tried to replicate the functionality based on this commit. So to wrap it up;
BTF Parsing and Collection (aya-obj/src/obj.rs):
Added collect_ksyms_from_btf() to parse .ksyms DATASEC from BTF
Implemented ExternCollection to store extern symbol metadata
Created dummy variable handling for function externs in DATASEC
Added BTF fixup logic via fixup_ksyms_datasec() to make externs kernel-acceptable
Extern Type System (aya-obj/src/extern_types.rs):
Implemented ExternDesc to track extern functions and variables
Added resolve_extern_ksyms() for resolving externs against kernel BTF
Created separate resolution paths for functions (resolve_extern_function_internal) and variables (resolve_extern_variable_internal)
Added type compatibility checking using types_are_compatible()
For variables, if they cant be resolved against target kernel btf, resolution through kallsyms is implemented as a fallback
Relocation Support (aya-obj/src/relocation.rs):
Added relocate_externs() to patch LD_IMM64 and CALL instructions with resolved kernel BTF IDs
Implemented instruction patching for both function calls and variable references.
Integration (aya/src/bpf.rs):
Integrated extern resolution into the EbpfLoader flow
Added kernel BTF loading and extern resolution before program loading
What we are missing:
1- We need more integration tests to cover different use-cases to make sure everything is properly implemented.
Some of the tests that needs to be covered are:
2- We are missing externs in kernel modules, this is something that is supported by libbpf, at this point it should not be that hard to implement it but before that i would like to first make sure the changes i introduce to aya aligns with the aya infra structure in terms of API desing.
3- Kconfigs are also missing, but again once this iteration goes through, it would be easier to extend this to cover Kconfigs as well.
I would really appreciate your feedback on this and let me know if i am missing anything else, or my implementation is not properly covering the described use-cases.
This change is